package com.atomos.utils
{
	import com.bourre.structures.Dimension;	
	
	import flash.geom.Point;
	
	import com.bourre.structures.Range;	

	/**
	 * An utility class which offer many convenient methods to manipulate
	 * random numbers. The class provides two main approach, the <i>absolute
	 * random</i> one, which use the native random mechanism  provided by
	 * Flash, and the <i>pseudo-random<i> one, which is based on the 
	 * <i>Mersenne Twister</i> algorithm.
	 * <p>
	 * The <code>RandomUtils</code> decline the same methods in three groups :
	 * <ul>
	 * <li><b>Float methods</b> which have no prefix.</li>	 * <li><b>Int methods</b> which are prefixed with <code>i</code>.
	 * These methods simply wraps calls to the float methods in a 
	 * <code>Math.floor</code> call.</li>
	 * <li><b>Pseudo-random float methods</b> which are prefixed with 
	 * <code>s</code>. These methods are nor based on the native 
	 * <code>Math.random</code> method, but on the <i>Mersenne Twister</i>
	 * algorithm for pseudo random number generation.</li>
	 * </ul>
	 * </p>
	 * @author Cédric Néhémie
	 */
	public class RandomUtils
	{
		/*
		 * FLOAT METHODS
		 */		
		/**
		 * Returns a random float value from 0 to the passed-in
		 * value included. If the argument is omitted, the
		 * returned value will be in the range <code>0 - 1</code>.
		 * 
		 * @param	val		bound value for the random value
		 * @return 	a random float in the range <code>0 - val</code>
		 * 			or in the range <code>0 - 1</code> 
		 * 			if <code>val</code> is omitted
		 */
		static public function random ( val : Number = 1 ) : Number
		{
			return Math.random() * val;
		}
		
		/**
		 * Returns a random float value equally distributed around
		 * <code>0</code> using the passed-in value as length for
		 * the range. 
		 * For example, when specifying a value of <code>10</code>
		 * in the <code>balance</code> method, the return will
		 * be contained between <code>-5</code> and <code>5</code>.
		 * <p>
		 * That method is used all along Atomos to modulate values
		 * in initialization.
		 * </p>
		 * @param	val	length of the balance range
		 * @return 	a random float around <code>0</code> at a maximum 
		 * 			distance of <code>val / 2</code>
		 * @example Building a simple random modifier for a value :
		 * <listing>function modulate( originalValue : Number, randomModulation : Number = 0 ) : Number
		 * {
		 * 	return originalValue + RandomUtils.balance( randomModulation );
		 * }</listing>
		 */
		static public function balance ( val : Number = 2 ) : Number
		{
			return Math.random() * val - val / 2;
		}
		
		/**
		 * Returns a random float within the passed-in range.
		 * 
		 * @param	r	potential values range
		 * @return 	a random float in the range
		 * @example <listing>var randValue : Number = RandomUtils.range( new Range( 10, 20 ) );</listing>
		 */
		static public function range ( r : Range ) : Number
		{
			return r.min + Math.random() * r.size();
		}
		
		/**
		 * Returns a point clone of the passed-in point randomized with
		 * the <code>rand</code> argument. More formally, the function
		 * clone the point argument and add to its coordinates a random
		 * value for each axis such :
		 * 
		 * <listing>randomValue = balance( randomAmount );</listing>
		 * 
		 * @param 	p		point to randomized.
		 * @param 	rand 	amount of random balance
		 * @return 	a randomized clone of the passed-in point
		 */
		static public function point ( p : Point, rand : Number ) : Point
		{
			var vec : Point = p.clone();
			vec.x += balance( rand );
			vec.y += balance( rand );
			return vec;
		}
		
		/**
		 * Returns a dimension clone of the passed-in dimension randomized
		 * with the <code>rand</code> argument. More formally, the function
		 * clone the dimension argument and add to its axis a random value
		 * for each axis such :
		 * 
		 * <listing>randomValue = balance( randomAmount );</listing>
		 * 
		 * @param 	d		dimension to randomized.
		 * @param 	rand 	amount of random balance
		 * @return 	a randomized clone of the passed-in dimension
		 */
		static public function dimension ( d : Dimension, rand : Number ) : Dimension
		{
			var dim : Dimension = d.clone();
			dim.width += balance( rand );
			dim.height += balance( rand );
			return dim;
		}
		
		/*
		 * INT METHODS
		 */
		/**
		 * Returns a random int value from 0 to the passed-in
		 * value included. If the argument is omitted, the
		 * returned value will be in the range <code>0 - 1</code>.
		 * 
		 * @param	val		bound value for the random value
		 * @return 	a random int in the range <code>0 - val</code>
		 * 			or in the range <code>0 - 1</code> 
		 * 			if <code>val</code> is omitted
		 */
		static public function irandom ( val : int = 1 ) : Number
		{
			return Math.round( Math.random() * val );
		}		
		/**
		 * Returns a random int value equally distributed around
		 * <code>0</code> using the passed-in value as length for
		 * the range. 
		 * For example, when specifying a value of <code>10</code>
		 * in the <code>ibalance</code> method, the return will
		 * be contained between <code>-5</code> and <code>5</code>.
		 * <p>
		 * That method is used all along Atomos to modulate values
		 * in initialization.
		 * </p>
		 * @param	val	length of the balance range
		 * @return 	a random int around <code>0</code> at a maximum 
		 * 			distance of <code>val / 2</code>
		 * @example Building a simple random modifier for a value :
		 * <listing>function modulate( originalValue : Number, randomModulation : Number = 0 ) : Number
		 * {
		 * 	return originalValue + RandomUtils.ibalance( randomModulation );
		 * }</listing>
		 */
		static public function ibalance ( val : int = 2 ) : Number
		{
			return Math.round( Math.random() * val - val / 2 );
		}		
		/**
		 * Returns a random int within the passed-in range.
		 * 
		 * @param	r	potential values range
		 * @return 	a random int in the range
		 * @example <listing>var randValue : Number = RandomUtils.irange( new Range( 10, 20 ) );</listing>
		 */
		static public function irange ( r : Range ) : Number
		{
			return Math.round( r.min + Math.random() * r.size() );
		}
		/**
		 * Returns a point clone of the passed-in point randomized with
		 * the <code>rand</code> argument. More formally, the function
		 * clone the point argument and add to its coordinates a random
		 * value for each axis such :
		 * 
		 * <listing>randomValue = ibalance( randomAmount );</listing>
		 * 
		 * @param 	p		point to randomized.
		 * @param 	rand 	amount of random balance
		 * @return 	a randomized clone of the passed-in point
		 */
		static public function ipoint ( p : Point, rand : int ) : Point
		{
			var vec : Point = p.clone();
			vec.x += ibalance( rand );
			vec.y += ibalance( rand );
			return vec;
		}
		/**
		 * Returns a dimension clone of the passed-in dimension randomized
		 * with the <code>rand</code> argument. More formally, the function
		 * clone the dimension argument and add to its axis a random value
		 * for each axis such :
		 * 
		 * <listing>randomValue = ibalance( randomAmount );</listing>
		 * 
		 * @param 	d		dimension to randomized.
		 * @param 	rand 	amount of random balance
		 * @return 	a randomized clone of the passed-in dimension
		 */
		static public function idimension ( d : Dimension, rand : Number ) : Dimension
		{
			var dim : Dimension = d.clone();
			dim.width += ibalance( rand );
			dim.height += ibalance( rand );
			return dim;
		}
		
		/*
		 * MERSENNE TWISTER RANDOM NUMBER GENERATOR
		 * Original implementation from Matthew Lloyd
		 * http://www.devslash.com/
		 */	
		/**
		 * Returns a pseudo-random float value from 0 to the passed-in
		 * value included. If the argument is omitted, the
		 * returned value will be in the range <code>0 - 1</code>.
		 * 
		 * @param	val		bound value for the random value
		 * @return 	a pseudo-random float in the range <code>0 - val</code>
		 * 			or in the range <code>0 - 1</code> 
		 * 			if <code>val</code> is omitted
		 */
		static public function srandom( val : Number = 1 ) : Number
		{
			return ( seed() / 0xffffffff ) * val;
		}	
		/**
		 * Returns a pseudo-random float value equally distributed around
		 * <code>0</code> using the passed-in value as length for
		 * the range. 
		 * For example, when specifying a value of <code>10</code>
		 * in the <code>sbalance</code> method, the return will
		 * be contained between <code>-5</code> and <code>5</code>.
		 * 
		 * @param	val	length of the balance range
		 * @return 	a pseudo-random float around <code>0</code> at a maximum 
		 * 			distance of <code>val / 2</code>
		 * @example Building a simple random modifier for a value :
		 * <listing>function modulate( originalValue : Number, randomModulation : Number = 0 ) : Number
		 * {
		 * 	return originalValue + RandomUtils.sbalance( randomModulation );
		 * }</listing>
		 */
		static public function sbalance ( val : Number = 2 ) : Number
		{
			return srandom( val ) - val / 2;
		}		
		/**
		 * Returns a pseudo-random float within the passed-in range.
		 * 
		 * @param	r	potential values range
		 * @return 	a pseudo-random float  in the range
		 * @example 
		 * <listing>var randValue : Number = RandomUtils.srange( new Range( 10, 20 ) );</listing>
		 */
		static public function srange ( r : Range ) : Number
		{
			return r.min + srandom() * r.size();
		}
		/**
		 * Returns a point clone of the passed-in point randomized with
		 * the <code>rand</code> argument. More formally, the function
		 * clone the point argument and add to its coordinates a random
		 * value for each axis such :
		 * 
		 * <listing>randomValue = sbalance( randomAmount );</listing>
		 * 
		 * @param 	p		point to randomized.
		 * @param 	rand 	amount of random balance
		 * @return 	a randomized clone of the passed-in point
		 */
		static public function spoint ( p : Point, rand : int ) : Point
		{
			var vec : Point = p.clone();
			vec.x += sbalance( rand );
			vec.y += sbalance( rand );
			return vec;
		}
		/**
		 * Returns a dimension clone of the passed-in dimension randomized
		 * with the <code>rand</code> argument. More formally, the function
		 * clone the dimension argument and add to its axis a random value
		 * for each axis such :
		 * 
		 * <listing>randomValue = sbalance( randomAmount );</listing>
		 * 
		 * @param 	d		dimension to randomized.
		 * @param 	rand 	amount of random balance
		 * @return 	a randomized clone of the passed-in dimension
		 */
		static public function sdimension ( d : Dimension, rand : Number ) : Dimension
		{
			var dim : Dimension = d.clone();
			dim.width += sbalance( rand );
			dim.height += sbalance( rand );
			return dim;
		}
		/**
		 * Returns a pseudo-random int according to the current
		 * <i>seed</i> value of the generator. Values returned
		 * by the <code>seed</code> method will always be the
		 * same for a given seed. The returned value is in the
		 * range <code>0 > 0xffffffff</code>.
		 * 
		 * @return	a pseudo-random number value
		 */	
		static public function seed():Number  
		{  
			// if there's no pre-generated values we plant a seed
			if( MT.length == 0 )
				plantSeed( 0 );
				
			/*
			 * if we have already use all the pre-generated values
			 * we generate new values
			 */
		    if( Z >= 623 )  
		    	generateNumbers(); 
		    
		    return extractNumber( Z++ );  
		}
		/**
		 * Initiate the pseudo-random number generator with the
		 * passed-in <code>seed</code>. Two distinct call with the
		 * same <code>seed</code> parameter will allways produce the
		 * same series of values.
		 * 
		 * @param	seed	a number used as seed for all generated
		 * 					random values
		 */
		static public function plantSeed( seed : Number ) : void  
		{  
			MT[0] = seed;  
		   
			for( var i : int = 1 ; i < 623 ; i++ )  
				MT[i] = ( ( 0x10dcd * MT[ i-1 ] ) + 1 ) & 0xFFFFFFFF; 
		}
		/**
		 * The array containing the pre-generated random values.
		 */
		static private var MT:Array = new Array();  
		/**
		 * The pointer to the next value to return in the 
		 * <code>seed</code> method.
		 */
		static private var Z:Number = 0;
		/**
		 * Used to generate new random values from the ones
		 * which are already in <code>MT</code>.
		 */
		static private var Y:Number = 0;
		
		/**
		 * Generates a new random values series and fill 
		 * the <code>MT</code> array with them. 
		 */
		static private function generateNumbers():void  
		{  
			Z = 0;
			   
			for( var i : int = 0 ; i < 623 ; i++ )  
			{  
				Y = 0x80000000 & MT[ i ] + 0x7FFFFFFF & ( MT[ ( i + 1 ) % 624 ] ); 
				  
				if( ( Y % 2 ) == 0 )  
					MT[ i ] = MT [ ( i + 397 ) % 624 ] ^ ( Y >> 1 );  
				else  
					MT[ i ] = MT[ ( i + 397 ) % 624 ] ^ ( Y >> 1 ) ^ 0x9908B0DF;  
			}  
		}   
   		/**
   		 * Returns the random values located at index <code>i</code>
   		 * in the <code>MT</code> array.
   		 * 
   		 * @param	i	index of the value to extract
   		 * @return	the random value located at i
   		 */
		static private function extractNumber( i : Number ):Number  
		{  
			Y = MT[ i ];  
			Y ^= ( Y >> 11 );  
			Y ^= ( Y << 7  ) & 0x9d2c5680;  
			Y ^= ( Y << 15 ) & 0xefc60000;  
			Y ^= ( Y >> 18 );  
			return Y;  
		}  
	
		/*
		 * MISC METHODS
		 */
		/**
		 * Returns a random boolean value according to the <code>percentage</code>
		 * argument. That argument represent the probability of having <code>true</code>
		 * returned by the function.
		 *  
		 * @param	percentage	the amount of <code>true</code> values returned
		 * 						by the function
		 * @return	a random boolean value according to the <code>percentage</code>
		 * 			argument
		 */
		static public function boolean ( percentage : Number = 0.5 ) : Boolean
		{
			return random() <= percentage;
		}
		/**
		 * Returns a random bit (0|1) according to the <code>percentage</code>
		 * argument. That argument represent the probability of having <code>1</code>
		 * returned by the function.
		 * 
		 * @param	percentage	the amount of <code>1</code> returned
		 * 						by the function
		 * @return	a random bit according to the <code>percentage</code>
		 * 			argument
		 */
		static public function bit ( percentage : Number = 0.5 ) : Number
		{
			return random() <= percentage ? 1 : 0;
		}
		/**
		 * Returns a random sign (-1|1) according to the <code>percentage</code>
		 * argument. That argument represent the probability of having <code>1</code>
		 * returned by the function.
		 * 
		 * @param	percentage	the amount of <code>1</code> returned
		 * 						by the function
		 * @return	a random sign according to the <code>percentage</code>
		 * 			argument
		 */
		static public function sign ( percentage : Number = 0.5 ) : Number
		{
			return random() <= percentage ? 1 : -1;
		}
		
		/**
		 * Returns a random ascii character.
		 * 
		 * @return a random ascii character
		 */
		static public function ascii () : String
		{
			return String.fromCharCode( irandom( 128 ) );
		}
		
	}
}